package so.sao.code.service.service.impl;

import static so.sao.code.service.infrastructure.EventsPublisher.publish;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.Lists;

import so.sao.code.dto.request.EnterpriseSetting;
import so.sao.code.service.domain.entity.EnterpriseInfo;
import so.sao.code.service.domain.entity.EnterpriseOperationLog;
import so.sao.code.service.domain.entity.TagBatch;
import so.sao.code.service.domain.entity.TagBatchDownloadLog;
import so.sao.code.service.domain.event.ChangeChildAllocEvent;
import so.sao.code.service.domain.event.ChangeChildCodeUpEvent;
import so.sao.code.service.infrastructure.SessionContext;
import so.sao.code.service.infrastructure.annotation.Permission;
import so.sao.code.service.infrastructure.dozer.Mapper;
import so.sao.code.service.infrastructure.enums.CodeMessage;
import so.sao.code.service.infrastructure.enums.ROLE;
import so.sao.code.service.infrastructure.enums.SEQ_TYPE;
import so.sao.code.service.infrastructure.model.vo.EnterpriseInfoVO;
import so.sao.code.service.infrastructure.model.vo.TagBatchDownloadLogVO;
import so.sao.code.service.infrastructure.utils.Obfuscated;
import so.sao.code.service.repository.EnterpriseInfoDao;
import so.sao.code.service.repository.EnterpriseOperationLogDao;
import so.sao.code.service.repository.TagBatchDao;
import so.sao.code.service.repository.TagBatchDownloadLogDao;
import so.sao.code.service.service.EnterpriseInfoService;
import so.sao.data.dto.response.EnterpriseDTO;
import so.sao.data.feign.client.EnterpriseClient;
import so.sao.top.core.model.enums.CommonStatus;
import so.sao.top.core.util.rest.RestException;

/**
 * Created by Administrator on 2017/10/19.
 */
@Service
public class EnterpriseInfoServiceImpl implements EnterpriseInfoService {

	@Autowired
	SessionContext context;
	@Autowired
	EnterpriseInfoDao enterpriseInfoDao;
	@Autowired
	EnterpriseOperationLogDao enterpriseOperationLogDao;

	@Autowired
	EnterpriseClient enterpriseClient;

	@Autowired
	TagBatchDownloadLogDao tagBatchDownloadLogDao;
	
	@Autowired
	TagBatchDao tagBatchDao;
	@Permission(role = ROLE.ADMIN)
	@Override
	public void addUrl(String url, Long eseId) {
		EnterpriseInfo enterpriseInfo = enterpriseInfoDao.findByEseId(eseId);
		enterpriseInfo.addUrl(url);
	}

	// @Permission(role = ROLE.ADMIN)
	@Override
	@Transactional
	public void delUrl(String url, Long eseId) {
		EnterpriseInfo enterpriseInfo = enterpriseInfoDao.findByEseId(eseId);
		enterpriseInfo.delUrl(url);
	}

	@Override
	public EnterpriseInfoVO get(Long eseId) {
		// EnterpriseInfo enterpriseInfo =
		// enterpriseInfoDao.findByEseId(context.CURRECT_USER.getEseId());
		EnterpriseInfo enterpriseInfo = enterpriseInfoDao.findByEseId(eseId);
		if (enterpriseInfo == null) {
			throw new RestException(HttpStatus.NOT_IMPLEMENTED, CodeMessage.EnterpriseInfoError);
		}
		EnterpriseDTO enterpriseInfoDto = enterpriseClient.findById(eseId);
		EnterpriseInfoVO result = Mapper.map(enterpriseInfo, EnterpriseInfoVO.class);
		result.setSeqType(enterpriseInfo.getSeqType().getValue());
		// result.setEseName(context.CURRECT_USER.getEseName());
		result.setEseName(enterpriseInfoDto.getName());
		return result;
	}

	public List<EnterpriseInfoVO> list() {
		List<EnterpriseDTO> enterpriseDtos = enterpriseClient.findSelections(context.CURRECT_USER.getEseId()).stream()
				.filter(item -> item.getStatus().equals(CommonStatus.EFFECTIVE.getValue()))
				.collect(Collectors.toList());
		List<EnterpriseInfo> enterpriseInfos = enterpriseInfoDao
				.findByEseIdIn(enterpriseDtos.stream().map(item -> item.getId()).collect(Collectors.toList()));
		enterpriseDtos.remove(0);// 第一个是自己 移除掉
		List<EnterpriseInfoVO> result = enterpriseDtos.stream().map(item -> {
			EnterpriseInfoVO tmp = new EnterpriseInfoVO();
			EnterpriseInfo enterpriseInfo = enterpriseInfos.stream()
					.filter(info -> info.getEseId().equals(item.getId())).findFirst().orElseGet(() -> {
						// 数据同步
						EnterpriseInfo def = new EnterpriseInfo();
						def.setEseId(item.getId());
						def.setUpCount(0L);
						def.setSeqType(SEQ_TYPE.BIG);
						enterpriseInfoDao.save(def);
						return def;
					});
			tmp.setEseName(item.getName());
			tmp.setEseId(enterpriseInfo.getEseId());
			tmp.setObf(Obfuscated.create(String.valueOf(enterpriseInfo.getEseId())));
			tmp.setLimit(enterpriseInfo.isLimit());
			tmp.setLimitCount(enterpriseInfo.getLimitCount());
			tmp.setUpCount(enterpriseInfo.getUpCount());
			tmp.setUrls(enterpriseInfo.getUrls());
			tmp.setSeqType(enterpriseInfo.getSeqType().getValue());
			tmp.setCreatedCount(enterpriseInfo.getCreatedCount());
			tmp.setAdvEncrypt(enterpriseInfo.getAdvEncrypt());
			// Long remain = enterpriseInfo.getUpCount() - enterpriseInfo.getCreatedCount();
			tmp.setRemainCount(enterpriseInfo.getUpCount());
			return tmp;
		}).collect(Collectors.toList());
		return result;
	}

	public void enterpriseCodeSetting(EnterpriseSetting setting) throws Exception {
		if (!setting.getObf().equals(Obfuscated.create(String.valueOf(setting.getEseId())))) {
			throw new RestException(HttpStatus.BAD_REQUEST, CodeMessage.InvalidRequest);
		}
		changeChildCodeUp(setting);
		changeChildAlloc(setting.getEseId(), setting.isLimit(), setting.getLimitCount());
	}

	/**
	 * 修改企业码序号
	 */
	@Permission(role = ROLE.ADMIN)
	@Transactional
	public void advSetting(Long eseId, boolean seqType, boolean advEncrypt) {
		EnterpriseInfo enterpriseInfo = enterpriseInfoDao.findByEseId(eseId);
		enterpriseInfo.setSeqType(seqType ? SEQ_TYPE.SMALL : SEQ_TYPE.BIG);
		enterpriseInfo.setAdvEncrypt(advEncrypt);
		enterpriseInfoDao.save(enterpriseInfo);
	}

	/**
	 * 修改子企业的码量上限
	 */
	public void changeChildCodeUp(EnterpriseSetting setting) throws Exception {
		EnterpriseInfo parent = enterpriseInfoDao.findByEseId(context.CURRECT_USER.getEseId());
		EnterpriseInfo enterpriseInfo = enterpriseInfoDao.findByEseId(setting.getEseId());
		Long temCount = setting.getCount();
		if (!setting.isAppend()) {
			// 算出差间 对于父企业 存在正负
			temCount = setting.getCount() - enterpriseInfo.getUpCount();
			// 初始化原有上限 upcount直接加上调整数量
			enterpriseInfo.setUpCount(0L);
		}
		parent.setUpCount(parent.getUpCount() - temCount);
		parent.setCreatedCount(parent.getCreatedCount() + temCount);

		enterpriseInfo.setUpCount(enterpriseInfo.getUpCount() + setting.getCount());
		if (parent.isLimit() && enterpriseInfo.getUpCount() > parent.getLimitCount()) {
			throw new RestException(HttpStatus.BAD_REQUEST, "调整不能大于限制");
		}
		enterpriseInfoDao.save(Lists.newArrayList(parent, enterpriseInfo));
		publish(new ChangeChildCodeUpEvent(setting.getEseId(), setting.getCount(), setting.isAppend(), context.ip));
	}

	/**
	 * 限制子企业分配下一级的码量
	 */
	public void changeChildAlloc(long eseId, boolean limit, Long count) throws Exception {
		EnterpriseInfo enterpriseInfo = enterpriseInfoDao.findByEseId(eseId);
		if (enterpriseInfo.getUpCount() < count) {
			throw new RestException(HttpStatus.BAD_REQUEST, "限制不能大于企业可用码量");
		}
		enterpriseInfo.setLimit(limit);
		enterpriseInfo.setLimitCount(count);
		enterpriseInfoDao.save(enterpriseInfo);
		publish(new ChangeChildAllocEvent(eseId, limit, count, context.ip));
	}

	/**
	 * 操作日志
	 */
	public List<EnterpriseOperationLog> getActionLog(int page, int size) {
		Pageable pageable = new PageRequest(page, size, Sort.Direction.DESC, "id");
		Specification<EnterpriseOperationLog> spec = new Specification<EnterpriseOperationLog>() {
			@Override
			public Predicate toPredicate(Root<EnterpriseOperationLog> root, CriteriaQuery<?> query,
					CriteriaBuilder cb) {
				List<Predicate> predicates = new ArrayList<Predicate>();
				predicates.add(cb.equal(root.get("createUserId").as(Long.class), context.CURRECT_USER.getId()));
				Predicate[] parr = new Predicate[predicates.size()];
				return cb.and(predicates.toArray(parr));
			}
		};
		Page<EnterpriseOperationLog> logs = enterpriseOperationLogDao.findAll(spec, pageable);
		return logs.getContent();
	}

	/**
	 * 下载日志
	 */
	@Override
	public Page<TagBatchDownloadLogVO> getDownloadLogs(Specification<TagBatchDownloadLog> spec, Pageable pageable) {
		Page<TagBatchDownloadLog> logs = tagBatchDownloadLogDao.findAll(spec, pageable);
		Page<TagBatchDownloadLogVO> result = logs.map(new Converter<TagBatchDownloadLog, TagBatchDownloadLogVO>() {
			@Override
			public TagBatchDownloadLogVO convert(TagBatchDownloadLog source) {
				if (source == null) {
					source = new TagBatchDownloadLog();
				}
				return Mapper.map(source, TagBatchDownloadLogVO.class);
			}
		});
		return result;
	}


	/**
	 * 同步基础数据
	 */
	public void initData() {
		List<EnterpriseInfo> result = new ArrayList<>();
		List<EnterpriseDTO> enterpriseDTOS = enterpriseClient.findAllEffective();
		List<EnterpriseInfo> enterpriseInfos = enterpriseInfoDao.findAll();
		List<TagBatch> tagBatches = tagBatchDao.findAll();
		enterpriseDTOS.stream().forEach(currentEnterprise -> {
			EnterpriseInfo enterpriseInfo = enterpriseInfos.stream()
					.filter(info -> info.getEseId().equals(currentEnterprise.getId())).findFirst().orElseGet(() -> {
						EnterpriseInfo def = new EnterpriseInfo();
						def.setEseId(currentEnterprise.getId());
						def.setSeqType(SEQ_TYPE.BIG);
						Long createdCount = tagBatches.stream()
								.filter(tagBatch -> tagBatch.getEseId().equals(currentEnterprise.getId()))
								.mapToLong(tagbatch_1 -> tagbatch_1.getTagCount()).sum();
						Long allocedCount = enterpriseDTOS.stream()
								.filter(enterpriseDTO -> enterpriseDTO.getParentId() != null
										&& enterpriseDTO.getParentId().equals(currentEnterprise.getId()))
								.mapToLong(enterpriseDTO_1 -> enterpriseDTO_1.getMaxTagCnt()).sum();
						if (!currentEnterprise.getId().equals(1L)) {
							def.setUpCount(currentEnterprise.getMaxTagCnt() - allocedCount - createdCount);
							// def.setCreatedCount(allocedCount);
						} else {
							allocedCount = allocedCount + enterpriseDTOS.stream()
									.filter(enterpriseDTO -> !enterpriseDTO.getId().equals(1L)
											&& enterpriseDTO.getParentId() == null
											&& enterpriseDTO.getTopId().equals(enterpriseDTO.getId()))
									.mapToLong(enterpriseDTO_1 -> enterpriseDTO_1.getMaxTagCnt()).sum();
							// def.setCreatedCount(allocedCount);
							def.setUpCount(0L);
							def.setSeqType(SEQ_TYPE.SMALL);
						}
						def.setCreatedCount(createdCount + allocedCount);
						return def;
					});
			result.add(enterpriseInfo);
		});
		enterpriseInfoDao.save(result);
	}

}
